﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;

namespace Mines.Ajax
{
    /// <summary>
    /// 摘自http://blog.zhaojie.me/2008/11/invoke-method-by-lambda-expression.html
    /// </summary>
    public static class DynamicMethodExecutor
    {
        #region DynamicMethodExecutor
        private static readonly Hashtable htDM = Hashtable.Synchronized(new Hashtable(10240));

        /// <summary>
        /// 实例范型方法/实例静态范型方法调用
        /// </summary>
        /// <param name="methodInfo"></param>
        /// <param name="instance"></param>
        /// <param name="parameters"></param>
        public static object Execute(this MethodInfo methodInfo, object instance, Type[] t, params object[] parameters)
        {
            var m_execute = GetExecuteDelegate(methodInfo, t);

            //var method = new DynamicMethod("", methodInfo.ReturnType, t, true);

            //ILGenerator il = method.GetILGenerator();

            //for (var i = 0; i < t.Length; i++)
            //{
            //    il.Emit(OpCodes.Ldarg, i);
            //    if (t[i].IsValueType)
            //        il.Emit(OpCodes.Unbox_Any, t[i]);
            //    else
            //        il.Emit(OpCodes.Castclass, t[i]);
            //}

            return m_execute(instance, parameters);
        }

        /// <summary>
        /// 实例方法/实例静态方法调用
        /// </summary>
        /// <param name="methodInfo"></param>
        /// <param name="instance"></param>
        /// <param name="parameters"></param>
        /// <returns></returns>
        public static object Execute(this MethodInfo methodInfo, object instance, params object[] parameters)
        {
            var m_execute = GetExecuteDelegate(methodInfo, null);
            return m_execute(instance, parameters);
        }

        /// <summary>
        /// 静态类方法/范型方法调用
        /// </summary>
        /// <param name="methodInfo"></param>
        /// <param name="t"></param>
        /// <param name="parameters"></param>
        /// <returns></returns>
        public static object Execute(this MethodInfo methodInfo, Type[] t, params object[] parameters)
        {
            var m_execute = GetStaticExecuteDelegate(methodInfo, t);
            return m_execute(parameters);
        }
        private static void Ldc(ILGenerator il, int value)
        {
            switch (value)
            {
                case -1:
                    il.Emit(OpCodes.Ldc_I4_M1);
                    return;
                case 0:
                    il.Emit(OpCodes.Ldc_I4_0);
                    return;
                case 1:
                    il.Emit(OpCodes.Ldc_I4_1);
                    return;
                case 2:
                    il.Emit(OpCodes.Ldc_I4_2);
                    return;
                case 3:
                    il.Emit(OpCodes.Ldc_I4_3);
                    return;
                case 4:
                    il.Emit(OpCodes.Ldc_I4_4);
                    return;
                case 5:
                    il.Emit(OpCodes.Ldc_I4_5);
                    return;
                case 6:
                    il.Emit(OpCodes.Ldc_I4_6);
                    return;
                case 7:
                    il.Emit(OpCodes.Ldc_I4_7);
                    return;
                case 8:
                    il.Emit(OpCodes.Ldc_I4_8);
                    return;
            }
 
            if (value > -129 && value < 128)
                il.Emit(OpCodes.Ldc_I4_S, (sbyte)value);
            else
                il.Emit(OpCodes.Ldc_I4, value);
        }

        private static Func<object, object[], object> GetExecuteDelegate(MethodInfo methodInfo, Type[] t)
        {
            Func<object, object[], object> res = (Func<object, object[], object>)htDM[methodInfo];
            if (res == null)
            {
                ParameterExpression instanceParameter = Expression.Parameter(typeof(object), "instance");
                ParameterExpression parametersParameter = Expression.Parameter(typeof(object[]), "parameters");
                List<Expression> parameterExpressions = new List<Expression>();
                ParameterInfo[] paramInfos = methodInfo.GetParameters();
                for (int i = 0; i < paramInfos.Length; i++)
                {
                    BinaryExpression valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i));
                    UnaryExpression valueCast = Expression.Convert(valueObj, paramInfos[i].ParameterType);
                    parameterExpressions.Add(valueCast);
                }
                Expression instanceCast = methodInfo.IsStatic ? null : Expression.Convert(instanceParameter, methodInfo.ReflectedType);
                var methodCall = Expression.Call(instanceCast, methodInfo.Name, t, parameterExpressions.ToArray());
                if (methodCall.Type == typeof(void))
                {
                    Expression<Action<object, object[]>> lambda = Expression.Lambda<Action<object, object[]>>(methodCall, instanceParameter, parametersParameter);

                    Action<object, object[]> execute = lambda.Compile();
                    res = (instance, parameters) =>
                    {
                        execute(instance, parameters);
                        return null;
                    };
                }
                else
                {
                    UnaryExpression castMethodCall = Expression.Convert(methodCall, typeof(object));
                    Expression<Func<object, object[], object>> lambda = Expression.Lambda<Func<object, object[], object>>(castMethodCall, instanceParameter, parametersParameter);
                    res = lambda.Compile();
                }
            }

            htDM[methodInfo] = res;

            return res;
        }

        private static Func<object[], object> GetStaticExecuteDelegate(MethodInfo methodInfo, Type[] t)
        {
            Func<object[], object> res = (Func<object[], object>)htDM[methodInfo];
            if (res == null)
            {
                ParameterExpression parametersParameter = Expression.Parameter(typeof(object[]), "parameters");
                // build parameter list
                List<Expression> parameterExpressions = new List<Expression>();
                ParameterInfo[] paramInfos = methodInfo.GetParameters();
                for (int i = 0; i < paramInfos.Length; i++)
                {
                    // (Ti)parameters[i]
                    BinaryExpression valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i));
                    UnaryExpression valueCast = Expression.Convert(valueObj, paramInfos[i].ParameterType);
                    parameterExpressions.Add(valueCast);
                }
                // static invoke or ((TInstance)instance).Method
                MethodCallExpression methodCall = Expression.Call(methodInfo.ReflectedType, methodInfo.Name, t, parameterExpressions.ToArray());
                // ((TInstance)instance).Method((T0)parameters[0], (T1)parameters[1], ...)
                if (methodCall.Type == typeof(void))
                {
                    var execute = Expression.Lambda<Action<object[]>>(methodCall, parametersParameter).Compile();
                    res = (parameters) => { execute(parameters); return null; };
                }
                else
                {
                    UnaryExpression castMethodCall = Expression.Convert(methodCall, typeof(object));
                    Expression<Func<object[], object>> lambda = Expression.Lambda<Func<object[], object>>(castMethodCall, parametersParameter);
                    res = lambda.Compile();
                }
            }
            return res;
        }
        #endregion
    }
}